import * as Y from 'yjs'
import { WebsocketProvider } from 'y-websocket'
import { getSnapshot, loadSnapshot, type TLStore } from '@tldraw/editor'

export type YjsBinding = {
	provider: WebsocketProvider
	doc: Y.Doc
	awareness: any
	teardown: () => void
}

// Very simple snapshot-based sync. For production, prefer granular record sync.
export function bindStoreToYjs(opts: {
	store: TLStore
	roomId: string
	wsUrl: string
	user?: { name?: string; color?: string }
}): YjsBinding {
	const { store, roomId, wsUrl, user } = opts

	const doc = new Y.Doc()
	const provider = new WebsocketProvider(wsUrl, roomId, doc, { connect: true })
	const awareness = provider.awareness
	const yMap = doc.getMap('tl')

	// presence
	awareness.setLocalStateField('user', {
		name: user?.name ?? `User-${Math.floor(Math.random() * 1000)}`,
		color: user?.color ?? `hsl(${Math.floor(Math.random() * 360)}, 70%, 55%)`,
	})

	let isApplyingRemote = false
	let isApplyingLocal = false
	let flushTimer: number | undefined

	const scheduleFlush = () => {
		if (flushTimer) return
		flushTimer = window.setTimeout(() => {
			flushTimer = undefined
			if (isApplyingRemote) return
			isApplyingLocal = true
			try {
				const snapshot = getSnapshot(store)
				yMap.set('snapshot', snapshot)
				yMap.set('ts', Date.now())
			} finally {
				isApplyingLocal = false
			}
		}, 50)
	}

	// Apply remote snapshot changes
	const handleYUpdate = () => {
		if (isApplyingLocal) return
		const remote = yMap.get('snapshot') as any
		if (!remote) return
		isApplyingRemote = true
		try {
			loadSnapshot(store, remote)
		} finally {
			isApplyingRemote = false
		}
	}

	yMap.observeDeep(handleYUpdate)

	// Seed: if no remote snapshot, publish local; else load remote
	const remoteSeed = yMap.get('snapshot') as any
	if (remoteSeed) {
		isApplyingRemote = true
		try {
			loadSnapshot(store, remoteSeed)
		} finally {
			isApplyingRemote = false
		}
	} else {
		scheduleFlush()
	}

	// Listen local store changes
	const unsub = store.listen(() => {
		scheduleFlush()
	})

	const teardown = () => {
		try { unsub() } catch {}
		try { provider.destroy() } catch {}
		try { doc.destroy() } catch {}
	}

	return { provider, doc, awareness, teardown }
}
